#include "Renderer.h"
#include "Log.h"

namespace GL
{

    Renderer::~Renderer()
    {
    }

    void Renderer::Start()
    {
    }

    void Renderer::CreateIndexesBuffer(unsigned int indexes[])
    {
        unsigned int EBO;
        glGenBuffers(1, &EBO);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(float), indexes, GL_STATIC_DRAW);
    }

    void Renderer::CreateVertexBuffer(float vertexs[])
    {

        unsigned int vertexBuffer;

        glGenBuffers(1, &vertexBuffer);
        // 顶点缓冲数组
        //  创建顶点缓冲
        glGenVertexArrays(1, &VertexArray);

        // 绑定顶点缓冲
        glBindVertexArray(VertexArray);

        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, 32 * sizeof(float), vertexs, GL_STATIC_DRAW);
    }

    int Renderer::CreateShader(const std::string shaderSource, int type)
    {
        int shader = glCreateShader(type);
        const char *const source = shaderSource.c_str();

        glShaderSource(shader, 1, &source, nullptr);
        glCompileShader(shader);

        int success;
        char info[512];
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            glGetShaderInfoLog(shader, 512, NULL, info);
            GL_ERROR("shader compiled failed! please check your code");
            return -1;
        }

        return shader;
    }

    int Renderer::CreateVertexShader(const std::string shaderSource)
    {
        return CreateShader(shaderSource, GL_VERTEX_SHADER);
    }
    int Renderer::CreateFragmentShader(const std::string shaderSource)
    {
        return CreateShader(shaderSource, GL_FRAGMENT_SHADER);
    }

    int Renderer::CreateProgram(int vertexShader, int fragmentShader)
    {
        Program = glCreateProgram();
        glAttachShader(Program, vertexShader);
        glAttachShader(Program, fragmentShader);
        glLinkProgram(Program);
        int success;
        char info[512];
        glGetProgramiv(Program, GL_LINK_STATUS, &success);

        if (!success)
        {
            glGetProgramInfoLog(Program, 512, NULL, info);
            GL_ERROR("program linked failed! please check your code , {0}", info);
            return -1;
        }
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        return Program;
    }

    // Texture

    void Renderer::useProgram()
    {

        glUseProgram(Program);
        glBindVertexArray(VertexArray);
    }
    int Renderer::GetAttributeLocation(int program, const std::string name)
    {
        return glGetAttribLocation(program, name.c_str());
    }

    void Renderer::ReadShaderSource(const std::string &sourceUrl)
    {

        ShaderSourceData source = GlLoader::LoadShaderSource(sourceUrl);

        VertexSource = source.VertexSource;
        FragmentSource = source.FragmentSource;
    }

    void Renderer::SetTranslate(glm::vec3 &pos)
    {
        transformData.x = pos.x;
        transformData.y = pos.y;
        transformData.z = pos.z;
    }

    void Renderer::SetRotate(glm::vec3 &angle)
    {
        transformData.rx = angle.x;
        transformData.ry = angle.y;
        transformData.rz = angle.z;
    }
    void Renderer::SetScale(glm::vec3 &scale)
    {
        transformData.sx = scale.x;
        transformData.sy = scale.y;
        transformData.sz = scale.z;
    }

    void Renderer::SetMatrix()
    {

        // note: currently we set the projection matrix each frame, but since the projection matrix rarely changes it's often best practice to set it outside the main loop only once.

        int viewLocation = glGetUniformLocation(Program, "view");
        int modelLocation = glGetUniformLocation(Program, "model");
        int projectionLocation = glGetUniformLocation(Program, "projection");

        glm::mat4 ViewMatrix = viewMatrix;
        glm::mat4 ModelMatrix = modelMatrix;
        glm::mat4 ProjectionMatrix = projectionMatrix;
        ViewMatrix = glm::translate(ViewMatrix, glm::vec3(transformData.x, transformData.y, transformData.z));

        ModelMatrix = glm::rotate(ModelMatrix, glm::radians(transformData.rx), glm::vec3(1.0, 0.0, 0.0));
        ModelMatrix = glm::rotate(ModelMatrix, glm::radians(transformData.ry), glm::vec3(0.0, 1.0, 0.0));
        ModelMatrix = glm::rotate(ModelMatrix, glm::radians(transformData.rz), glm::vec3(0.0, 0.0, 1.0));

        ModelMatrix = glm::scale(ModelMatrix, glm::vec3(transformData.sx, transformData.sy, transformData.sz));

        ProjectionMatrix = glm::perspective(glm::radians(45.0f), (float)600 / (float)400, 0.1f, 100.0f);

        glUniformMatrix4fv(viewLocation, 1, GL_FALSE, &ViewMatrix[0][0]);
        glUniformMatrix4fv(modelLocation, 1, GL_FALSE, glm::value_ptr(ModelMatrix));
        glUniformMatrix4fv(projectionLocation, 1, GL_FALSE, &ProjectionMatrix[0][0]);
    }

    void Renderer::SetViewMatrix(glm::mat4 &view)
    {
        viewMatrix = view;
    };
    void Renderer::SetVec3(const std::string &attributeName, glm::vec3 &vec)
    {
        useProgram();
        int location = GetUniformLocation(Program, attributeName);

        glUniform3fv(location, 1, &vec[0]);
    }
    int Renderer::GetUniformLocation(int program, const std::string &name)
    {
        return glGetUniformLocation(program, name.c_str());
    }
}